<?php


namespace App\Game\Timer;


use App\Game\Conf\GameProgress;
use App\Game\Conf\SubCmd;
use App\Game\Core\Message;
use App\Game\Core\Robot;
use App\Game\Core\Room;
use App\Model\User;
use App\Service\Hupai;
use App\Service\PaiXing;
use App\Task\GameSyncTask;

class GameTimer
{
    public static $ASSIGN_ROBOT_TIMER = 0;   //投放机器人定时器
    public static $CHANGE_CARD_TIMER = 1;   //换牌定时器
    public static $CHOOSE_COLOR_TIMER = 2;  //定缺定时器
    public static $OUT_CARD_TIMER = 3;      //出牌定时器
    public static $CHECK_HPK_TIMER = 4;     //检查胡碰杠定时器

    public static $ADD_DJS_ROBOT_TIMER = 5;     //添加大奖赛机器人定时器

//    public static $CHANGE_CARD_TIMER_DELAY = 30;     //换牌定时器延时s
//    public static $CHOOSE_COLOR_TIMER_DELAY = 30;    //定缺定时器延时s
//    public static $OUT_CARD_TIMER_DELAY = 30;        //出牌定时器延时s
//    public static $CHECK_HPK_TIMER_DELAY = 30;       //检查胡碰杠定时器延时s

    public static function run($timer, $room_no, $wait_time)
    {

        $wait_time *= 1000;
        switch ($timer) {
            case self::$ASSIGN_ROBOT_TIMER :
                $name = 'assign_robot';
                break;
            case self::$CHANGE_CARD_TIMER :
                $name = 'change_card';
                break;
            case self::$CHOOSE_COLOR_TIMER:
                $name = 'choose_color';
                break;
            case self::$OUT_CARD_TIMER:
                $name = 'out_card';
                break;
            case self::$CHECK_HPK_TIMER:
                $name = 'check_hpg';
                break;
        }
        $time = time();
        $timerId = \Swoole\Timer::after($wait_time, function () use ($timer, $room_no, $name, $time) {
            $lastTimer = getLastTimer($room_no);
            dd('lastTimer', $lastTimer);
            dd('on ' . $name . ' timer', $time);
            if ($lastTimer && ($lastTimer['time'] != date('i:s', $time))) {
                dd($name . ' timer die ', $time);
                return;
            }
            switch ($timer) {
                case self::$CHANGE_CARD_TIMER :
                    ddTitle('change_card_timer room_no:' . $room_no);
                    self::do_change_card($room_no);
                    break;
                case self::$CHOOSE_COLOR_TIMER:
                    ddTitle('choose_color_timer :' . $room_no);
                    self::do_choose_color($room_no);
                    break;
                case self::$OUT_CARD_TIMER:
                    ddTitle('out_card_timer :' . $room_no);
                    self::do_out_card($room_no);
                    break;
                case self::$CHECK_HPK_TIMER:
                    ddTitle('check_hpk_timer :' . $room_no);
                    self::do_check_hpk($room_no);
                    break;
            }
        });

        ddTitle('add ' . $name . ' timer  id:' . $timerId . ' wait_time: ' . $wait_time);
        //添加定时器缓存
        addTimer($room_no, $timerId, $name, $time);
    }


    //添加机器人
    public static function do_assign_robots($room_no)
    {
        ddTitle('start do_assign_robots :' . $room_no);

        //检查游戏进度，如果不是当前进度，则不执行当前调用流程
        if (!checkGameProgress($room_no, GameProgress::GAME_JOIN)) {
//            ddNode('s1');
            if (checkGameProgress($room_no, GameProgress::GAME_PREPARE)) {
//                ddNode('s2');
                $prepareArr = getGamePrepareALL($room_no);
                if (count($prepareArr) > 1) {
//                    ddNode('s3');
                    ddTitle('exit by game progress, current progress: ' . getGameProgress($room_no) . ' need progress:' . GameProgress::GAME_JOIN);
                    return;
                }
            } else {
//                ddNode('s4');
                ddTitle('exit by game progress, current progress: ' . getGameProgress($room_no) . ' need progress:' . GameProgress::GAME_JOIN);
                return;
            }
        }
//        ddNode('s5');
//        ddNode(1);
        //添加操作计数器，防止重复操作
        addOperCounter($room_no);
        //检查计数器，如果计数器被增加了，表示操作被执行过，则不再重复执行该调用流程
        if (!checkCounter($room_no)) {
            ddTitle('exit by opercounter, current counter: ' . getCounter($room_no));
            return;
        }
//        ddNode(2);
        $robot_conf = config('robot');
        $waiters = getRoomWait($room_no);
        $chairArr = [];
        $prepareArr = getGamePrepareALL($room_no);
//        dd('prepareArr', $prepareArr);
        if (count($prepareArr) == 1) {
//            ddNode('s6');
            $preArr = array_keys($prepareArr);
//            dd('preArr', $preArr);
            $acc = $preArr[0];
            $chairArr[$acc] = getChairIdbyAccount($room_no, $acc);
//            dd('chairArr', $chairArr);
            $notPrepareArr = array_diff($waiters, $preArr);
//            dd('notPrepareArr', $notPrepareArr);
            $waiters = [$acc];
//            dd('waiters', $waiters);
            foreach ($notPrepareArr as $acc) {
                $msg = ['status' => '强制退出'];
                $serv = server();
                $fd = getFdByAccount($acc);
                pushDataToUser($serv, $fd, $msg, SubCmd::FORCE_QUIT_ROOM_RESP);
                $msg = ['status' => '退出房间', 'chair_id' => getChairIdbyAccount($room_no, $acc)];
                broadCast($room_no, $msg, SubCmd::SUB_GAME_ROOM_QUIT_RESP);
                //清空该玩家数据
                removeRoomMapByAccount($acc);
                removeRoomWaitByAccount($room_no, $acc);
                removeGamePrepareByAccount($room_no, $acc);
                removeRoomUserDataByAccount($room_no, $acc);
                removeRoomChairByAccount($room_no, $acc);
            }
            removeRoomRobot($room_no); //去除房间之前的机器人
        } else {
            return;
        }
        //投放机器人
        $rand_arr = array_rand($robot_conf['robots'], (4 - count($waiters)));
        $rand_no_arr = [];
        foreach ($rand_arr as $v) {
            $waiters[] = $robot_conf['robots'][$v];
            $score = mt_rand(2000, 9587);
            addRoomRobot($room_no, $robot_conf['robots'][$v], $score);
            $rand_no_arr[$robot_conf['robots'][$v]] = $score;
        }
//        dd('waiters', $waiters);
        setRoomWaitByArr($room_no, $waiters);

        //设置游戏进度类型为单机
        setGameProgressType($room_no, 1);

        //更新游戏进度为准备，重置操作计数器
        setGameProgress($room_no, GameProgress::GAME_CHANGE_CARD);
        resetOperCounter($room_no);
//        ddNode(3);
        $game_conf = config('game');
        //设置座位
        $room_chair_key = sprintf($game_conf['user_room_chair'], $room_no);


        if (empty($chairArr)) {
            $room_chair_data = [
                $waiters[0] => 1,
                $waiters[1] => 2,
                $waiters[2] => 3,
                $waiters[3] => 4,
            ];
        } else {
            $selectedChairds = array_values($chairArr);
            $unSelectedChairds = array_diff([1, 2, 3, 4], $selectedChairds);
            $room_chair_data = [];
            foreach ($waiters as $acc) {
                $room_chair_data[$acc] = isset($chairArr[$acc]) ? $chairArr[$acc] : array_pop($unSelectedChairds);
            }
        }


//        dd('room_chair_data', $room_chair_data);
        redis()->set($room_chair_key, json_encode($room_chair_data));// 绑定用户房间号桌位号
        $fd = getFdByAccount($waiters[0]);
        $realAcc = $waiters[0];
//        dd('fd', $fd);
        $uinfo = getUserInfoByFd($fd);
//        dd('uinfo', $uinfo);
        if (empty($uinfo)) {
            return;
        }
//        ddNode(4);
        $serv = server();
        $master = Room::getRoomMaster($room_no);
        $match_players[] = ['account' => $uinfo['account'], 'nickname' => filterNickname($uinfo['nickname']), 'avatar' => $uinfo['avatar'], 'score' => $uinfo['score'], 'chair_id' => 1];
//        dd('match_players', $match_players);
        foreach ($rand_no_arr as $acc => $score) {
            sleep(2);
            //发送加入消息
            $info = User::userInfo($acc);
            $chair_id = $room_chair_data[$acc];
//            $score = mt_rand(5000, 20000);
            $match_players[] = ['account' => $info['account'], 'nickname' => filterNickname($info['nickname']), 'avatar' => $info['avatar'], 'score' => $score, 'chair_id' => $chair_id];
            $room_data = ['status' => 'waiting', 'players' => $match_players, 'room_no' => $room_no];
            $waiters = getRoomWait($room_no);
            if (!in_array($realAcc, $waiters)) {
                return;
            }
            pushDataToUser($serv, $fd, $room_data, SubCmd::SUB_GAME_WAIT);
            usleep(300);
            //发送准备消息
            $msg = ['status' => 0, 'chair_id' => $chair_id, 'master' => $master];
            pushDataToUser($serv, $fd, $msg, SubCmd::SUB_GAME_PREPARE_RESP);
        }
        sleep(2);
        //发牌
        $task = container()->get(GameSyncTask::class);
        $task->assignCards($room_no, $master);
    }


    //换牌
    public static function do_change_card($room_no, $user_changes = [])
    {
        ddTitle('start do_change_card:' . $room_no);
        //检查游戏进度，如果不是当前进度，则不执行当前调用流程
        if (!checkGameProgress($room_no, GameProgress::GAME_CHANGE_CARD)) {
            ddTitle('exit by game progress, current progress: ' . getGameProgress($room_no) . ' need progress:' . GameProgress::GAME_CHANGE_CARD);
            return;
        }
        //添加操作计数器，防止重复操作
        addOperCounter($room_no);
        //检查计数器，如果计数器被增加了，表示操作被执行过，则不再重复执行该调用流程
        if (!checkCounter($room_no)) {
            ddTitle('exit by opercounter, current counter: ' . getCounter($room_no));
            return;
        }
        $game_conf = config('game');
        $user_conf = config('user');
        $serv = server();
//        $user_changes = getRoomChangeCards($room_no);
        $room_data = getRoomData($room_no);
        $user_chairs = getRoomChairs($room_no);

        if (empty($user_changes)) {
            $game_conf = config('game');
            $room_change_key = sprintf($game_conf['user_room_change'], $room_no);
            $arr = redis()->hGetAll($room_change_key);
            foreach ($arr as $k => $v) {
                $user_changes[$k] = json_decode($v, true);
            }
        }

        // 1.判断已操作换牌用户数，没满员则由系统自动给未操作玩家执行换牌
        dd('user_changes befor', $user_changes);
        $notOperUserArr = [];
        if (count($user_changes) < 4) {
            $changeKyes = array_keys($user_changes);
            foreach ($user_chairs as $k => $v) {
                if (!in_array($v, $changeKyes)) {
                    $notOperUserArr[] = ['account' => $k, 'chair_id' => $v];
                }
            }
        }
//        dd('notOperUserArr', $notOperUserArr);
        if (!empty($notOperUserArr)) {
//            ddNode('auto change card, players:' . count($notOperUserArr));
            foreach ($notOperUserArr as $v) {
                $user_data = json_decode($room_data[$v['account']], true);
                $user_changes[$v['chair_id']] = randChangeCard($user_data['card']);
            }
        }
//        dd('user_changes after', $user_changes);
        $changeArr = $user_changes;
//        dd('room_data', $room_data);
//        dd('user_chairs', $user_chairs);
        dd('changeArr', $changeArr);
        $cardIn = [];
        $robot_cards = [];
        $change_card = $changeArr;//更换的牌
        $ptype = getGameProgressType($room_no);
        foreach ($user_chairs as $k => $v) {
            switch ($v) {
                case 1:
                    $charNo = 4;
                    break;
                case 2:
                    $charNo = 3;
                    break;
                case 3:
                    $charNo = 2;
                    break;
                case 4:
                    $charNo = 1;
                    break;
            }
            $changeCard = [];
            foreach ($user_chairs as $m) {
                if ($m == $charNo) {
                    $changeCard = $changeArr[$m];
                    $change_card[$charNo] = $changeArr[$m];
                }
            }
            //拿到玩家手牌
            $user_data = json_decode($room_data[$k], true);
//            dd('user_data_befor', $user_data);
//            dd('charNo', $charNo);
//            dd('v', $v);
//            dd('changeCard', $changeCard);
//            dd('changeArr[v]', $changeArr[$v]);
            //去掉选出的换牌
            foreach ($changeArr[$v] as $m) {
                foreach ($user_data['card'] as $n => $p) {
                    if ($m == $p) {
                        unset($user_data['card'][$n]);
                        break;
                    }
                }
            }
            sort($user_data['card']);
            $user_rest_cards = $user_data['card'];
//            dd('user_rest_cards', $user_rest_cards);
            //加入其他玩家的换牌
            $user_cards = array_merge($user_rest_cards, $changeCard);
            sort($user_cards);

            $cardNum = count($user_cards);
            if (!in_array($cardNum, [13, 14])) { //数据错乱，直接踢出所有玩家，并解散房间
                foreach ($user_chairs as $ac => $c) {
//                    ddTitle('pushForceQuitMsg: ' . $ac);
                    pushForceQuitMsg($room_no, $ac);
                    sleep(2);
                }
                return;
            }

            $user_data['card'] = $user_cards;
            if ($v == $room_data['master']) {
                $cardIn = ['card' => $user_cards[13], 'chair_id' => $v];
            }
            //更新房间玩家手牌数据
            $room_data[$k] = $user_data;
//            dd('room_data', $room_data);
            $is_robot = Robot::isRobot($room_no, $k);
            if ($is_robot) { //机器人
                $robot_cards[$k] = $user_data;
            }
        }
        $room_data['in'] = $cardIn;
//        dd('room_data', $room_data);


        if (!empty($robots)) { //单机
            //把机器人的手牌放回底牌池
            $cards_pool = json_decode($room_data['hand'], true);
            //随机分配机器人听牌
            $robot_level_up = env('ROBOT_LEVEL_UP', 0);
            if (!$robot_level_up) { //强化机器人（是否全部听牌）
                $robotNum = count($robot_cards);
                $randNum = rand(1, $robotNum);
                $robot_cards = array_rand($robot_cards, $randNum);
            }
            foreach ($robot_cards as $robot_data) {
                $cards_pool = array_merge($cards_pool, $robot_data['card']);
            }
            $temArr = [];
            $hand = [];
            for ($i = 0; $i < count($robot_cards); $i++) {
                $res = getRandomTingPaiCards($cards_pool);
                $temArr[] = $res;
                $hand = $res['rest_cards'];//剩余底牌
            }

            shuffle($hand);//打乱两次
            shuffle($hand);
            $index = 0;
            foreach ($robot_cards as $acc => $robot_data) {
                $room_data[$acc]['card'] = $temArr[$index]['cards'];
                addRoomUserColor($room_no, $robot_data['chair_id'], $temArr[$index]['color']);
                //庄家再多拿一张牌
                if ($room_data['master'] == $robot_data['chair_id']) {
                    $card = array_shift($hand);
                    $room_data[$acc]['card'][] = $card;
                    $cardIn = ['card' => $card, 'chair_id' => $robot_data['chair_id']];
                    $room_data['in'] = $cardIn;
                }
                $index++;
            }
            $room_data['hand'] = $hand;
        }

        dd('room_data', $room_data);

        //短局（测试用）
        $shortGame = env('SHORT_GAME', 0);
        if ($shortGame) {
            $room_data['hand'] = [1, 2, 3, 4, 5, 6, 7, 8, 9]; // 调试数据用
        }

        $room_data_key = sprintf($game_conf['user_room_data'], $room_no);
        arrToHashInRedis($room_data, $room_data_key);

        //更新游戏进度为定缺，重置操作计数器
        setGameProgress($room_no, GameProgress::GAME_CHOOSE_COLOR);
        resetOperCounter($room_no);

        dd('user_chairs', $user_chairs);

        //广播换牌结果，每个人收到的都是自己专属的牌组信息
        foreach ($user_chairs as $acc => $cid) {
            $fd = getFdByAccount($acc);
            if ($fd) {
                $arr = [
                    'card' => $room_data[$acc]['card'],
                    'chair_id' => $cid,
                    'change_card' => $changeArr[$cid]
                ];
                $plstatus = playerStatus($room_no, $acc);
                if ($plstatus == GameProgress::USER_ONLINE) { //玩家在线才能发送消息
                    pushDataToUser($serv, $fd, $arr, SubCmd::SUB_GAME_CHANGE_OVER_RESP);
                }
            }
        }


        //5秒后广播推送定缺
        delayBroadCast(3, $room_no, ['wait_time' => $user_conf['choose_timer']],
            SubCmd::SUB_GAME_START_CHOOSE_COLOR, self::$CHOOSE_COLOR_TIMER);
    }

    //定缺
    public static function do_choose_color($room_no, $user_colors = [])
    {
        ddTitle('start do_choose_color :' . $room_no);

        //检查游戏进度，如果不是当前进度，则不执行当前调用流程
        if (!checkGameProgress($room_no, GameProgress::GAME_CHOOSE_COLOR)) {
            ddTitle('exit by game progress, current progress: ' . getGameProgress($room_no) . ' need progress:' . GameProgress::GAME_CHOOSE_COLOR);
            return;
        }

        //添加操作计数器，防止重复操作
        addOperCounter($room_no);
        //检查计数器，如果计数器被增加了，表示操作被执行过，则不再重复执行该调用流程
        if (!checkCounter($room_no)) {
            ddTitle('exit by opercounter, current counter: ' . getCounter($room_no));
            return;
        }

        if (empty($user_colors)) {
            $game_conf = config('game');
            $room_color_key = sprintf($game_conf['user_room_color'], $room_no);
            $user_colors = redis()->hGetAll($room_color_key);
            if (!$user_colors) {
                $user_colors = [];
            }
        }
        $user_chairs = getRoomChairs($room_no);

        // 1.判断已操作换牌用户数，没满员则由系统自动给未操作玩家执行换牌
//        dd('user_colors', $user_colors);
        $notOperUserArr = [];
        if (count($user_colors) < 4) {
            $colorKyes = array_keys($user_colors);
            foreach ($user_chairs as $k => $v) {
                if (!in_array($v, $colorKyes)) {
                    $notOperUserArr[] = ['account' => $k, 'chair_id' => $v];
                }
            }
        }
//        dd('notOperUserArr', $notOperUserArr);
        if (!empty($notOperUserArr)) {
//            ddNode('auto choose color, players:' . count($notOperUserArr));
            foreach ($notOperUserArr as $v) {
                $color = randColor($room_no, $v['account']);
//                dd('randcoloruser', [$v, $color]);
                $user_colors[$v['chair_id']] = $color;
            }
        }
//        dd('user_colors', $user_colors);
        setRoomColors($room_no, $user_colors);
        $colorData = [];
        foreach ($user_colors as $k => $v) {
            $colorData[] = ['chair_id' => $k, 'que' => $v];
        }

        //更新游戏进度为出牌，重置操作计数器
        setGameProgress($room_no, GameProgress::GAME_OUT_CARD);
        resetOperCounter($room_no);


        delayBroadCast(0, $room_no, ['colors' => $colorData], SubCmd::SUB_GAME_CHOOSE_OVER_RESP);

        $room_data = getRoomData($room_no);
//        dd('room_data', $room_data);
        $master = $room_data['master'];
        updateRoomData($room_no, ['next_chair_id' => $master]);
        sleep(3);
        //发送出牌消息
//        delayBroadCast(3, $room_no, ['next_chair_id' => $master,
//            'wait_time' => $user_conf['play_timer']], SubCmd::SUB_GAME_OUT_CARD,
//            self::$OUT_CARD_TIMER);


        //广播出牌消息
        self::broadCastOutCard($room_no, $master, true);
    }

    //出牌
    public static function do_out_card($room_no, $out_cards = [], $auto = false)
    {
//        ddTitle('start do_out_card :' . $room_no . ' ' . date('i:s', time()));

        //检查游戏进度，如果不是当前进度，则不执行当前调用流程
        if (!checkGameProgress($room_no, GameProgress::GAME_OUT_CARD)) {
            ddTitle('exit by game progress, current progress: ' . getGameProgress($room_no) . ' need progress:' . GameProgress::GAME_OUT_CARD);
            return;
        }

        //添加操作计数器，防止重复操作
        addOperCounter($room_no);
        //检查计数器，如果计数器被增加了，表示操作被执行过，则不再重复执行该调用流程
        if (!checkCounter($room_no)) {
            ddTitle('exit by opercounter, current counter: ' . getCounter($room_no));
            return;
        }

        $room_data = getRoomData($room_no);
        $next_chair_id = $room_data['next_chair_id'];

        dd('out_cards befor', $out_cards);
        if (empty($out_cards)) { //系统出牌
            ddNode('c-1');
            //拿到摸牌信息作为系统自动出牌数据
            $out_cards = json_decode($room_data['in'], true);
            //如果上一个摸牌的人不是自己
            if ($next_chair_id != $out_cards['chair_id']) {
                ddNode('c-2');
                //2.如果上次摸牌数据不是当前用户，则直接打出牌组的最后一张(碰牌的情况)
                $account = getAccountByChairId($room_no, $next_chair_id);
                $user_data = json_decode($room_data[$account], true);
                $out_cards = ['card' => $user_data['card'][count($user_data['card']) - 1], 'chair_id' =>
                    $next_chair_id];
            }
        }

        dd('out_cards after', $out_cards);
        $chair_id = $out_cards['chair_id'];
        $card = $out_cards['card'];
        //本轮次上一次牌型
        $last_chair_id = isset($room_data['last_chair_id']) ? $room_data['last_chair_id'] : $chair_id;
        //下一个出牌人椅子id
        $next_chair_id = $chair_id + 1;
        $next_chair_id = ($next_chair_id > 4) ? $next_chair_id - 4 : $next_chair_id;

        //轮次
        $round = isset($room_data['round']) ? $room_data['round'] + 1 : 0;
        //手次
        $hand_num = isset($room_data['hand_num']) ? $room_data['hand_num'] + 1 : 1;

        $account = getAccountByChairId($room_no, $chair_id);
//        ddTitle("auto out card ，account: { $account }  chair_id: { $chair_id } ");
        //设置减少手牌数据
        $param = setMyOutCard($room_no, $out_cards, $account, $round, $hand_num,
            $last_chair_id, $next_chair_id);


//        dd('out card param', $param);
        ddTitle($room_no . ' player :' . $chair_id . ' out card [' . $card . '] end ');
        //更新游戏进度为检查胡碰杠，重置操作计数器，重置检查胡碰杠缓存
        setGameProgress($room_no, GameProgress::GAME_CHECK_HPG);
        resetOperCounter($room_no);
        resetCheckHpg($room_no);

        $out_result = [
            'status' => 0,
            'chair_id' => (int)$chair_id, //出牌椅子
            'card' => $out_cards['card'],        //本次出牌
        ];

        //如果是定时器执行的操作，则设置该玩家为托管状态
        if ($auto) {
            ddTitle('o3');
            setTakeOverByAccount($room_no, $account);
            broadCast($room_no, ['chair_id' => $chair_id, 'take_over' => 1], SubCmd::SUB_GAME_TAKE_OVER);
            sleep(1);
        }

        $is_robot = is_robot($room_no, $account);
        $robots = getRoomAllRobot($room_no);
        $robotNum = count($robots);
        ddTitle('o1');
        if (($robotNum == 3) && !$is_robot) { //单机
            ddTitle('o2');
            delayBroadCast(0.1, $room_no, $out_result, SubCmd::SUB_GAME_OUT_CARD_RESP);
            sleep(2);
            self::do_check_hpk($room_no, [], $out_cards['card']);
            return;
        }

        $flag = false;
        $user_checks = [];
        $prepareArr = getGamePrepareALL($room_no);
        foreach ($prepareArr as $acc => $status) {
            if ($acc != $account) {
                $user_checks[$account] = 0;
                $flag = false;
                if ($status != GameProgress::USER_ONLINE) {
                    $flag = true;
                }
            }
        }

        if ($flag) { //其他玩家全部掉线，则检查胡碰杠自动全部默认过牌，直接执行检查胡碰杠
            ddTitle('o4');
            //异步广播出牌消息，并执行检测胡碰杠
            delayBroadCast(0.1, $room_no, $out_result, SubCmd::SUB_GAME_OUT_CARD_RESP);
            sleep(4);
            //全是机器人检测胡碰杠
            self::do_check_hpk($room_no, $user_checks, $out_cards['card'], false, true);
        } else {
            ddTitle('o5');
            //异步广播出牌消息，并设置检查胡碰杠计时器
            delayBroadCast(0.1, $room_no, $out_result, SubCmd::SUB_GAME_OUT_CARD_RESP,
                self::$CHECK_HPK_TIMER);
        }

    }

    //检查胡碰杠 $auto:全是机器人检测胡碰杠
    public static function do_check_hpk($room_no, $user_checks = [], $card = 0, $zimo = false, $auto = false)
    {
//        ddTitle('start do_check_hpk :' . $room_no . ' ' . date('i:s', time()));
        //检查游戏进度，如果不是当前进度，则不执行当前调用流程
        if (!$zimo && !checkGameProgress($room_no, GameProgress::GAME_CHECK_HPG)) {
            ddTitle('exit by game progress, current progress: ' . getGameProgress($room_no) . ' need progress:' . GameProgress::GAME_CHECK_HPG);
            return;
        }

        //添加操作计数器，防止重复操作
        addOperCounter($room_no);
        //检查计数器，如果计数器被增加了，表示操作被执行过，则不再重复执行该调用流程
        if (!checkCounter($room_no)) {
            ddTitle('exit by opercounter, current counter: ' . getCounter($room_no));
            return;
        }


        $room_data = getRoomData($room_no);
        $hand = json_decode($room_data['hand'], true);
        $handNum = count($hand);
        //自摸的时候取自己摸的牌
        if ($zimo) {
            ddNode('d-0');
            $out_cards = json_decode($room_data['in'], true);
        } else {
            ddNode('d-1');
            $out_cards = json_decode($room_data['out'], true);
        }

        if ($card && $card > 0) {
            $out_cards['card'] = $card;
        }
        dd('out_cards', $out_cards);

        if (empty($out_cards)) {
            ddTitle('数据错误，没有出牌信息，请重置游戏');
            //TODO 发送错误提示重置游戏
            return;
        }


        $needCheckArr = array_diff([1, 2, 3, 4], [$out_cards['chair_id']]);

        if (empty($user_checks)) {
            foreach ($needCheckArr as $cid) {
                $account = getAccountByChairId($room_no, $cid);
                $user_checks[$account] = 0;
            }
        }
        $playerChecks = getCheckHPGArr($room_no);
        if (!empty($playerChecks)) {
            ddNode(2);
            foreach ($user_checks as $acc => $type) {
                if (isset($playerChecks[$acc])) {
                    $user_checks[$acc] = $playerChecks[$acc];
                }
            }
        }

        // 1.判断已操作检查胡碰杠用户数，没满员则由系统自动给未操作玩家执行过牌
//        dd('user_checks before', $user_checks);


        foreach ($user_checks as $k => $v) {
            $user_Data = json_decode($room_data[$k], true);
            if (isset($user_Data['hu']) && in_array($out_cards['card'], $user_Data['hu'])) {
                $user_checks[$k] = 1; //如果胡过的牌，可以接着胡
            }
        }


        if (!$zimo) { //非自摸
            //需要检测胡碰杠的座位集合
//            dd('out_cards', $out_cards);
//            dd('checkArr', $needCheckArr);
            foreach ($needCheckArr as $cid) {
                $acc = getAccountByChairId($room_no, $cid);
                $is_robot = Robot::isRobot($room_no, $acc);
                if (!$is_robot) {//真人玩家
                    continue;
                }
                if ($user_checks[$acc]) {
                    continue; //已经选择的跳过
                }
                $user_Data = json_decode($room_data[$acc], true);
                $arr = array_count_values($user_Data['card']);
                $pengArr = $user_Data['peng'];
                foreach ($arr as $k => $v) {
                    if ($k == $out_cards['card']) {
                        if ($v == 3 && !in_array($v, $pengArr)) {
                            $user_checks[$acc] = 2; //杠
                            continue;
                        }
                        if ($v == 2 && !in_array($v, $pengArr)) {
                            $user_checks[$acc] = 3; //碰
                            continue;
                        }
                    }
                }
                if ($handNum <= 30) { //剩余30张牌后机器人开始检测胡牌
                    $user_Data['card'][] = $out_cards['card'];
                    $hu = (new Hupai())->checkhu($user_Data['card']);
                    if ($hu) {
                        $user_checks[$acc] = 1; //胡
                        continue;
                    }
                }
            }
        }

//        dd('user_checks after', $user_checks);


        $flag = true; //执行失败标志，true=执行失败 false=执行成功
        $huArr = [];//当前出牌可以胡牌人数
        foreach ($user_checks as $account => $v) {
            if ($v == 1) { //胡牌
                $huArr[] = $account;
            }
        }
        if (count($huArr)) {
            ddNode('d-1');
            $beforGang = false; //抢杠胡
            foreach ($user_checks as $v) {
                if ($v == 2) { //检查胡碰杠时，同时有胡牌和杠牌，则胡牌优先，且触发抢杠胡，
                    $beforGang = true;
                }
            }
            $piaoArr = [];
            $next_chair_id = 0;
            foreach ($huArr as $account) {
                $res = checkHu($room_no, $out_cards, $account, $handNum, $zimo, $beforGang);
//                dd('account', $account);
//                dd('hu-res', $res);
                if ($res['status']) {
                    $flag = true;
                    continue;
                }
                $flag = false;
                if (!$next_chair_id) { //第一个成功胡牌的人，确定下一家出牌的顺序
                    $next_chair_id = $res['data']['next_chair_id'];
                }

                $is_robot = Robot::isRobot($room_no, $account);
                if ($is_robot) {//机器人的时候延迟以下推送消息，以免速度太快不正常
                    sleep(2);
                }

                ddTitle($room_no . ' player :' . $v . ' hu card [' . $out_cards['card'] . '] end ');
//                $is_robot = is_robot($room_no, $account);
                if ($is_robot && !$zimo) {
                    //广播胡牌
                    broadCast($room_no, $res['data'], SubCmd::SUB_GAME_HU_CARD_RESP);
                }
                $piaoArr[] = $res['data']['piao']; //piao = [['chair_id' => 1, 'score' => -2],['chair_id' => 2, 'score' => 2]]
                usleep(500);
            }
//            dd('piaoArr', $piaoArr);
            $temp = [];
            foreach ($piaoArr as $arr) {
                foreach ($arr as $piao) {
                    @$temp[$piao['chair_id']] += $piao['score'];
                }
            }
            $piaoData = [];
            foreach ($temp as $cid => $score) {
                $piaoData[] = ['chair_id' => $cid, 'score' => (int)$score];
            }
//            dd('piaoData', $piaoData);
            //广播飘分
            broadCast($room_no, ['piao' => $piaoData], SubCmd::SUB_GAME_PIAO_SCORE);
            if (!$flag) {
                if (count($huArr) == 3) { //如果一炮三响，则继续有放炮玩家摸牌
                    $next_chair_id = $out_cards['chair_id'];
                }
                if (count($huArr) == 2) { //如果一炮二响，则由没有胡的玩家摸牌
                    foreach ($user_checks as $account => $v) {
                        if ($v == 0) { //没有胡牌
                            $next_chair_id = getChairIdbyAccount($room_no, $account);
                        }
                    }
                }
                //执行发牌
                $next_account = getAccountByChairId($room_no, $next_chair_id);
                self::sendCard($room_no, $next_account, $next_chair_id, $handNum, false, true);
                return;
            }
        }
        if ($flag) {
            foreach ($user_checks as $account => $v) {
                if ($v == 2) { //杠牌
                    ddNode('d-2');
//                    dd('out_cards', $out_cards);
                    $res = checkGang($room_data, $out_cards, $account, $zimo);
//                    dd('account', $account);
//                    dd('gang-res', $res);
                    if ($res['status']) {
//                        pushErr($serv, $fd, $scmd, $res['info']);
                        $flag = true;
                        break;
                    }
                    $chair_id = findChairId($room_no, $account); // 座位号
                    if ($ptype && $chair_id != 1) {//单机,且是机器人的时候延迟以下推送消息，以免速度太快不正常
                        sleep(2);
                    }
                    //广播杠牌通知
                    broadCast($room_no, ['card' => $out_cards['card'],
                        'chair_id' => (int)$res['data']['chair_id'], 'from_chair_id' => (int)$res['data']['from_chair_id']],
                        SubCmd::SUB_GAME_GANG_CARD_RESP);

                    //广播飘分
                    broadCast($room_no, ['piao' => $res['data']['piao']], SubCmd::SUB_GAME_PIAO_SCORE);

                    $next_account = $account;

                    //执行发牌
                    self::sendCard($room_no, $next_account, $chair_id, $handNum, true);
                    return;
                } else {
                    $flag = true;
                }
            }
        }
        if ($flag) {
            foreach ($user_checks as $account => $v) {
                if ($v == 3) { //碰牌
                    ddNode('d-3');
                    $res = checkPeng($room_data, $out_cards['card'], $account);
//                    dd('account', $account);
//                    dd('peng-res', $res);
                    if ($res['status']) {
//                        pushErr($serv, $fd, $scmd, $res['info']);
                        $flag = true;
                        break;
                    }
                    $chair_id = getChairIdbyAccount($room_no, $account);
                    if (Robot::isRobot($room_no, $account)) {//机器人的时候延迟以下推送消息，以免速度太快不正常
                        sleep(2);
                    }
                    ddNode('do_check_hpk end d-3');
                    //更新游戏进度为出牌，重置操作计数器
                    setGameProgress($room_no, GameProgress::GAME_OUT_CARD);
                    resetOperCounter($room_no);
                    resetCheckHpg($room_no);
                    //广播碰牌的信息
                    broadCast($room_no, ['status' => 0, 'card' => $out_cards['card'],
                        'chair_id' => $chair_id, 'from_chair_id' => $res['data']['from_chair_id']],
                        SubCmd::SUB_GAME_PENG_OVER_RESP);

                    //判断底牌剩余数，没有则游戏结束
                    if (self::checkGameOver($room_no, $handNum)) {
                        return;
                    }

                    ddTitle($room_no . ' player :' . $chair_id . ' is next to out card');
                    //广播出牌消息
                    self::broadCastOutCard($room_no, $chair_id);
                    return;
                } else {
                    $flag = true;
                }
            }
        }
        if ($flag) {
            ddNode('d-4');
            if ($zimo) { //这一步很重要！自摸情况下，上面流程报错就打出当前摸到的牌并终止后续操作，否则就重复发牌
                ddNode('d-4 zimo');
                self::outLastCard($room_data, $out_cards['chair_id'], true);
                return;
            }
            //下一个出牌人椅子id
            $next_chair_id = $room_data['next_chair_id']; // 座位号
            $next_account = getAccountByChairId($room_no, $next_chair_id);
            //执行发牌
            self::sendCard($room_no, $next_account, $next_chair_id, $handNum);
        }
    }


    public static function checkGameOver($room_no, $handNum, $is_gang_hu = false)
    {
        //判断底牌剩余数，没有则游戏结束
        if ($handNum < 1) {
            ddTitle('游戏已结束');
//            if ($is_gang_hu) { //胡杠之后游戏结束，延迟4秒发送结算消息
            sleep(4);
//            }
//            gameOver($room_no);
            Room::gameOver($room_no);
            return true;
        }
        return false;
    }

    //广播出牌消息
    public static function broadCastOutCard($room_no, $chair_id, $dataFromInCard = false)
    {
        $user_conf = config('user');
        $room_data = getRoomData($room_no);
        $account = getAccountByChairId($room_no, $chair_id);
        $is_robot = is_robot($room_no, $account);
//        $ptype = getGameProgressType($room_no);
        if ($is_robot) { //机器人回合直接出牌，不调用定时器
            delayBroadCast(1, $room_no, ['next_chair_id' => (int)$chair_id,
                'wait_time' => $user_conf['play_timer']], SubCmd::SUB_GAME_OUT_CARD);
            ddTitle('out card robot : ' . $chair_id);
            sleep(rand(1, 4));
            self::outLastCard($room_data, $chair_id, $dataFromInCard);
        } else { //真人回合调用定时器
            delayBroadCast(1, $room_no, ['next_chair_id' => (int)$chair_id,
                'wait_time' => $user_conf['play_timer']], SubCmd::SUB_GAME_OUT_CARD,
                self::$OUT_CARD_TIMER);
            $user_data = json_decode($room_data[$account], true);
            $plstatus = playerStatus($room_no, $account);
            if ($plstatus == GameProgress::USER_OFFLINE || $user_data['take_over']) {
                //如果托管或掉线，则自动打出当前的牌
                ddTitle('out card user take_over :' . $account . ' | ' . $chair_id);
                sleep(rand(1, 4));
                $room_data = getRoomData($room_no);
                if ($room_data['next_chair_id'] == $chair_id) { //非自己的回合不能自动出牌，否则数据错乱
                    self::outLastCard($room_data, $chair_id, $dataFromInCard);
                }
            }
        }
    }

    //打出用户最后一张牌
    public static function outLastCard($room_data, $chair_id, $dataFromInCard = false)
    {
        ddTitle('outLastCard: ' . $chair_id . ' | dataFromInCard: ' . $dataFromInCard);
        $room_no = $room_data['room_no'];
        $account = getAccountByChairId($room_no, $chair_id);
        //本轮次上一次牌型
        $last_chair_id = $chair_id;
        //下一个出牌人椅子id
        $next_chair_id = $chair_id + 1;
        $next_chair_id = ($next_chair_id > 4) ? $next_chair_id - 4 : $next_chair_id;
        //如果上一个摸牌的人不是自己
        if (!$dataFromInCard) {
            ddNode('c-2');
            //2.如果上次摸牌数据不是当前用户，则直接打出牌组的最后一张(碰牌的情况)
            $account = getAccountByChairId($room_no, $chair_id);
            $user_data = json_decode($room_data[$account], true);
            $out_cards = ['card' => $user_data['card'][count($user_data['card']) - 1], 'chair_id' => $chair_id];
        } else {
            //拿到摸牌信息作为系统自动出牌数据
            $out_cards = json_decode($room_data['in'], true);
        }
        $is_robot = is_robot($room_no, $account);
        if ($is_robot) { //机器人
            $hand = json_decode($room_data['hand'], true);
            $handNum = count($hand);
            if ($handNum <= 30) {
                //检测自摸
                $res = checkHu($room_no, $out_cards, $account, $handNum, true);
//                dd('account', $account);
//                dd('hu-res', $res);
                if (!$res['status']) {
//                    ddTitle($room_no . ' player :' . $account . ' hu card [' . $out_cards['card'] . '] end ');
                    //广播胡牌
                    Message::broadCast($room_no, $res['data'], SubCmd::SUB_GAME_HU_CARD_RESP);
                    //广播飘分
                    Message::broadCast($room_no, ['piao' => $res['data']['piao']], SubCmd::SUB_GAME_PIAO_SCORE);
                    //执行发牌
                    $next_account = getAccountByChairId($room_no, $next_chair_id);
                    self::sendCard($room_no, $next_account, $next_chair_id, $handNum);
                    return;
                }
            }
            $card = self::getQueCard($room_no, $chair_id);
            if ($card) {
                $out_cards['card'] = $card;
            }
        }
        $card = $out_cards['card'];

        //轮次
        $round = isset($room_data['round']) ? $room_data['round'] + 1 : 0;
        //手次
        $hand_num = isset($room_data['hand_num']) ? $room_data['hand_num'] + 1 : 1;


        ddTitle("auto out card ，account: { $account }  chair_id: { $chair_id } ");
        $out_cards['chair_id'] = $chair_id;
        //设置减少手牌数据
        $param = setMyOutCard($room_no, $out_cards, $account, $round, $hand_num,
            $last_chair_id, $next_chair_id);
        ddTitle($room_no . ' player :' . $chair_id . ' out card [' . $card . '] end ');
        //更新游戏进度为检查胡碰杠，重置操作计数器，重置检查胡碰杠缓存
        setGameProgress($room_no, GameProgress::GAME_CHECK_HPG);
        resetOperCounter($room_no);
        resetCheckHpg($room_no);

        $out_result = [
            'status' => 0,
            'chair_id' => $chair_id, //出牌椅子
            'card' => $out_cards['card'],        //本次出牌
        ];
        //删除前一个定时器
        putTimerDel($room_no);


        $robots = getRoomAllRobot($room_no);
        $robotNum = count($robots);
        ddTitle('o1');
        if (($robotNum == 3) && !$is_robot) { //单机
            ddTitle('o2');
            delayBroadCast(0.1, $room_no, $out_result, SubCmd::SUB_GAME_OUT_CARD_RESP);
            sleep(2);
            self::do_check_hpk($room_no, [], $out_cards['card']);
            return;
        }

        $flag = false;
        $user_checks = [];
        $prepareArr = getGamePrepareALL($room_no);
        foreach ($prepareArr as $acc => $status) {
            if ($acc != $account) {
                $user_checks[$account] = 0;
                $flag = false;
                if ($status != GameProgress::USER_ONLINE) {
                    $flag = true;
                }
            }
        }

        if ($flag) { //其他玩家全部掉线，则检查胡碰杠自动全部默认过牌，直接执行检查胡碰杠
            ddTitle('o4');
            //异步广播出牌消息，并执行检测胡碰杠
            delayBroadCast(0.1, $room_no, $out_result, SubCmd::SUB_GAME_OUT_CARD_RESP);
            sleep(3);
            //全是机器人检测胡碰杠
            self::do_check_hpk($room_no, $user_checks, $out_cards['card'], false, true);
        } else {
            ddTitle('o5');
            //异步广播出牌消息，并设置检查胡碰杠计时器
            delayBroadCast(0.1, $room_no, $out_result, SubCmd::SUB_GAME_OUT_CARD_RESP,
                self::$CHECK_HPK_TIMER);
        }

//        //异步广播出牌消息，并设置检查胡碰杠计时器
//        delayBroadCast(0.1, $room_no, $out_result, SubCmd::SUB_GAME_OUT_CARD_RESP,
//            GameTimer::$CHECK_HPK_TIMER);
    }

    public static function sendCard($room_no, $next_account, $chair_id, $handNum, $is_gang = false, $is_hu = false)
    {
        $flag = false;//是否胡或者杠之后发牌
        if ($is_gang || $is_hu) {
            $flag = true;
        }
        //判断底牌剩余数，没有则游戏结束
        if (self::checkGameOver($room_no, $handNum, $flag)) {
            return;
        }

        $res = sendCard($room_no, $next_account, $chair_id, $is_gang);
//            dd('account', $account);
//        dd('sendCard-res', $res);
        if ($res['status']) {
            return false;
        }
        //更新游戏进度为出牌
        setGameProgress($room_no, GameProgress::GAME_OUT_CARD);
        //重置操作计数器
        resetOperCounter($room_no);
        resetCheckHpg($room_no);
        //给自己发送通知，包含所摸牌的信息,给其他人发送通知，不含所摸牌的信息
        \Swoole\Timer::after(300, function () use (
            $room_no, $res, $next_account,
            $chair_id
        ) {
            ddTitle($room_no . ' player :' . $chair_id . ' send card [' .
                $res['data']['card'] . '] end ');
            $waters = getRoomWait($room_no);
            foreach ($waters as $account) {
                $is_robot = Robot::isRobot($room_no, $account);
                if ($is_robot) {
                    continue;
                }
                if ($account == $next_account) {
                    Message::pushDataToPlayer($account, ['card' => $res['data']['card'], 'chair_id' => (int)$chair_id, 'hand_num' => $res['data']['hand_num']], SubCmd::SUB_GAME_SEND_CARD);
                } else {
                    Message::pushDataToPlayer($account, ['chair_id' => (int)$chair_id, 'hand_num' => $res['data']['hand_num']], SubCmd::SUB_GAME_SEND_CARD);
                }
//                $plstatus = playerStatus($room_no, $account);
//                if ($plstatus == GameProgress::USER_ONLINE && !$is_robot) { //玩家在线才能发送消息
//                    $fd = getFdByAccount($account);
//                    if ($account == $next_account) {
//                        pushDataToUser($serv, $fd, ['card' => $res['data']['card'], 'chair_id' => (int)$chair_id, 'hand_num' => $res['data']['hand_num']], SubCmd::SUB_GAME_SEND_CARD);
//                        Message::pushDataToPlayer();
//                    } else {
//                        pushDataToUser($serv, $fd, ['chair_id' => (int)$chair_id, 'hand_num' =>
//                            $res['data']['hand_num']], SubCmd::SUB_GAME_SEND_CARD);
//                    }
//                }
            }
        });
        ddTitle($room_no . ' player :' . $chair_id . ' is next to out card');
        //广播出牌消息
        self::broadCastOutCard($room_no, $chair_id, true);
        return true;
    }

    public static function getQueCard($room_no, $chair_id)
    {
        $room_data = getRoomData($room_no);
        $account = getAccountByChairId($room_no, $chair_id);
        $user_data = json_decode($room_data[$account], true);
        $cards = $user_data['card'];
        $colors = getRoomColors($room_no);
        $user_color = $colors[$chair_id];
        if ($user_color == 1) {
            $arr = [1, 2, 3, 4, 5, 6, 7, 8, 9];
        }
        if ($user_color == 2) {
            $arr = [11, 12, 13, 14, 15, 16, 17, 18, 19];
        }
        if ($user_color == 3) {
            $arr = [21, 22, 23, 24, 25, 26, 27, 28, 29];
        }
        foreach ($cards as $k => $v) {
            if (in_array($v, $arr)) {
                return $v;
            }
        }
        return false;
    }

}